package object

import (
	"bytes"
	"fmt"
	"github.com/emicklei/go-restful"
	"github.com/gorilla/schema"
	"github.com/jackc/pgx"
	"github.com/jackc/pgx/pgtype"
	"github.com/jinzhu/gorm"
	"gitlab.com/adapt-karaoke-project/backend/model"
	"gitlab.com/adapt-karaoke-project/backend/service/base"
	"io"
	"net/http"
	"strconv"
	"gitlab.com/adapt-karaoke-project/backend/config"
	"io/ioutil"
)

type ObjectService struct {
	dbService base.DBService
}

func NewObjectService(connPool *pgx.ConnPool, db *gorm.DB) *ObjectService {
	return &ObjectService{
		dbService: *base.NewDBService(connPool, db),
	}
}

func (os *ObjectService) Register(container *restful.Container) {
	ws := new(restful.WebService)
	ws.Path("/object-api")
	ws.Route(ws.POST("/objects").
		Consumes("multipart/form-data").
		Produces(restful.MIME_JSON).
		To(os.postObject))
	ws.Route(ws.GET("/object/{oid}").
		Produces(restful.MIME_OCTET).
		To(os.getObject)).
		Param(restful.PathParameter("oid", "Object id").
			DataType(fmt.Sprintf("%T", pgtype.OID(0))))
	container.Add(ws)
}

type postObjectRequest struct {
	Acl string `schema: "acl"`
}

type postObjectResponse struct {
	OId pgtype.OID `json:"oid"`
}

func (os *ObjectService) postObject(req *restful.Request, resp *restful.Response) {  //write database
	err := req.Request.ParseMultipartForm(50 * 1024 * 1024)
	if err != nil {
		resp.WriteErrorString(http.StatusBadRequest, err.Error())
		return
	}
	decoder := schema.NewDecoder()
	req_object := postObjectRequest{}
	err = decoder.Decode(&req_object, req.Request.MultipartForm.Value)
	fmt.Printf("%+v\n", req_object)
	fmt.Printf("%+v\n", req.Request.PostForm)
	if err != nil {
		resp.WriteErrorString(http.StatusBadRequest, err.Error())
		return
	}
	if req_object.Acl != "public" {
		resp.WriteErrorString(http.StatusBadRequest, "Currently only public ACL is supported")
		return
	}
	file_form := req.Request.MultipartForm.File
	datas, ok := file_form["data"]
	if !ok {
		resp.WriteErrorString(http.StatusBadRequest, "data field missing")
		return
	}
	if len(datas) != 1 {
		resp.WriteErrorString(http.StatusBadRequest, "Must contain 1 data field")
		return
	}
	data := datas[0]
	if data.Size > 30*1024*1024 {
		resp.WriteErrorString(http.StatusRequestEntityTooLarge, "Maximum object size is 30MB")
		return
	}
	file, err := data.Open()
	if err != nil {
		resp.WriteError(http.StatusBadRequest, err)
		return
	}
	defer file.Close()
	oid, err := model.CreateLargeObject(os.dbService.ConnPool, file)
	if err != nil {
		resp.WriteError(http.StatusInternalServerError, err)
		return
	}
	resp_obj := postObjectResponse{
		OId: oid,
	}
	resp.WriteEntity(resp_obj)
}

func (os *ObjectService) getObject(req *restful.Request, resp *restful.Response) {
	oid_str := req.PathParameter("oid")
	oid, err := strconv.ParseUint(oid_str, 10, 0)
	fmt.Println(oid)
	if err != nil {
		resp.WriteError(http.StatusBadRequest, err)
		return
	}

	buf := new(bytes.Buffer)
	get_large_object_err := model.GetLargeObject(os.dbService.ConnPool, pgtype.OID(oid), buf)
	if get_large_object_err != nil {
		switch get_large_object_err.Type {
		case model.GetLargeObjectErrorNotFound:
			resp.WriteError(http.StatusNotFound, get_large_object_err.Raw)
			return
		case model.GetLargeObjectErrorInternalError:
			resp.WriteError(http.StatusInternalServerError, get_large_object_err.Raw)
			return
		}
	}
	resp.WriteHeader(http.StatusOK)
	io.Copy(resp, buf)
}

func (os *ObjectService) Init(cfg config.InitConfig) {
	if cfg.AddStubData {
		for _, desc := range config.ObjStubs {
			model.DeleteLargeObject(os.dbService.ConnPool, pgtype.OID(desc.OID))
			data, err := ioutil.ReadFile(desc.Path)
			if err != nil {
				panic(err)
			}
			_, err = model.CreateLargeObjectWithId(os.dbService.ConnPool, pgtype.OID(desc.OID), bytes.NewReader(data))
			if err != nil {
				panic(err)
			}
		}
	}
}
